package com.webex.ta.hydra.core.engine;

import com.webex.ta.hydra.core.*;
import com.webex.ta.hydra.core.HydraParameter;
import com.webex.ta.hydra.core.Keyword;
import com.webex.ta.hydra.core.macro.Macro;
import com.webex.ta.hydra.util.HydraUtils;

import java.io.Serializable;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by Cisco WebEx.
 * User: vegaz
 * Date: 2010-8-26
 * Time: 17:06:53
 */
public class HydraThread implements Runnable, Serializable {
    public static final int DEBUG_MODE = 0;
    public static final int RUN_MODE = 1;

    private boolean running;
    private boolean paused;

    private TestPlan testplan;
    private HydraEngine engine;
    private transient HydraContext threadContext;

    private int mode;
    private ReentrantLock lock;
    private Condition condition;

    public HydraThread() {
        mode = RUN_MODE;
        running = true;
        paused = false;
        testplan = null;
        lock = new ReentrantLock();
        condition = lock.newCondition();
        init();
    }

    public void configure(TestPlan tp) {
        testplan = tp;
    }

    public void setEngine(HydraEngine engine) {
        this.engine = engine;
        threadContext.setEngine(engine);
    }

    public void turnOnDebugMode() {
        mode = DEBUG_MODE;
    }

    public void turnOffDebugMode() {
        mode = RUN_MODE;
    }

    public void stop() {
        running = false;
    }

    public void pause() {
        paused = true;
    }

    public void resume() {
        paused = false;
        lock.lock();
        condition.signalAll();
        lock.unlock();
    }

    public void run() {
        Command nextCmd;
        ((DefaultHydraEngine) engine).notifyTestListenersOfStart(this);
        while (running && (nextCmd = testplan.next()) != null) {
            threadContext.setCurrentCommand(nextCmd);

            Keyword kw = nextCmd.getRuntime();
            if (paused || (nextCmd.isBreakPoint() && mode == DEBUG_MODE)) {
                ((DefaultHydraEngine) engine).notifyTestListenersOfPause(this);
                try {
                    lock.lock();
                    condition.await();
                    ((DefaultHydraEngine) engine).notifyTestListenersOfResume(this);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return;
                } finally {
                    lock.unlock();
                }
            }

            if (kw != null) {
                if (kw instanceof Action) {
                    doAction(nextCmd);
                } else if (kw instanceof Processor) {
                    doProcessor(nextCmd);
                } else if (kw instanceof Assertion) {
                    doAssertion(nextCmd);
                }
            }
        }
        ((DefaultHydraEngine) engine).notifyTestListenersOfEnd(this);
    }

    private void doAction(Command cmd) {
        Action action = (Action) cmd.getRuntime();
        // generate string arguments from HydraParameter for later usage
        String[] args = getRealValue(action.getParameters());

        TestResult result = null;
        result = TestResult.createInstance(cmd);
        result.setKeyword(cmd.getKeyword());
        result.setArguments(args);

        try {
            action.execute(result, args);
        } catch (Exception e) {
            result.setError(true);
            result.setResponse(HydraUtils.printStackTraceToString(e));
        }
        threadContext.setPreviousResult(result);
        ((DefaultHydraEngine) engine).notifyActionListenerOfOccured(result);
    }

    private void doProcessor(Command cmd) {
        Processor processor = (Processor) cmd.getRuntime();
        String[] args = getRealValue(processor.getParameters());
        try {
            processor.process(args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doAssertion(Command cmd) {
        Assertion assertion = (Assertion) cmd.getRuntime();
        String[] args = getRealValue(assertion.getParameters());
        AssertionResult ar = new AssertionResult();
        ar.setKeyword(cmd.getKeyword());
        ar.setArguments(args);

        TestResult tr = threadContext.getPreviousResult();
        if (tr!= null) {
            assertion.doAssertion(tr, ar, args);
            ar.setParent(tr);
            tr.addAssertionResult(ar);
            if (ar.isError() || ar.isFailure())
                tr.setFailure(true);
            ((DefaultHydraEngine) engine).notifyAssertionListenerOfOccured(ar);
        }
    }
    
    private void init() {
        HydraContextService.reset();
        threadContext = HydraContextService.getContext();
    }

    public HydraContext getThreadContext() {
        if (threadContext == null)
            threadContext = HydraContextService.getContext();
        return threadContext;
    }

    public static String[] getRealValue(HydraParameter[] hps) {
        String[] strArgs = new String[hps.length];
        for (int i = 0; i < hps.length; i++) {
            strArgs[i] = hps[i].getStringValue();
        }
        return strArgs;
    }
}
